﻿using AutoMapper;
using Mes.Infrastructure.Data.BoundedContext.Extensions;
using Mes.Infrastructure.Data.BoundedContext.Repositories.Operations;
using Mes.Domain.BoundedContext;
using Mes.Domain.BoundedContext.Entities.Operations;
using Mes.Application.DTO.Inputs.Operations;
using Mes.Application.DTO.Results.Operations;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using Process = Mes.Domain.BoundedContext.Entities.Operations.Process;
using Mes.Application.Common;
using Mes.Infrastructure.Data.BoundedContext.Configurations;
using Microsoft.Extensions.Options;
using Mes.Application.Common.Resource;
using Mes.Infrastructure.Data.BoundedContext.Repositories.MasterData;

namespace Mes.Application.Services.Operations
{
    public class ProcessService : IProcessService
    {
        private readonly IMapper _mapper; 
        private readonly IProcessRepository _processRepository;
        private readonly IItemProcessLinkRepository _item_Process_LinkRepository;
        private readonly IMaterialRepository _materialRepository;
        private readonly IProcessRouteRepository _processRouteRepository;
        private readonly IOperRepository _operRepository;
        private readonly IOperEntRouteRepository _operEntRouteRepository;
        private readonly IOperService _operService;
        private readonly BuiltInTypesConfiguration _builtInTypes;

        public ProcessService(IMapper mapper, IProcessRepository processRepository, IItemProcessLinkRepository item_Process_LinkRepository,
            IMaterialRepository materialRepository, IProcessRouteRepository processRouteRepository, IOperRepository operRepository,
            IOperEntRouteRepository operEntRouteRepository, IOperService operService, IOptions<BuiltInTypesConfiguration> options)
        {
            if (mapper == null || processRepository == null || item_Process_LinkRepository == null || materialRepository == null
                || processRouteRepository == null || operRepository == null || operEntRouteRepository == null || operService == null)
            {
                throw new ArgumentNullException();
            }
            _mapper = mapper;
            _processRepository = processRepository;
            _item_Process_LinkRepository = item_Process_LinkRepository;
            _materialRepository = materialRepository;
            _processRouteRepository = processRouteRepository;
            _operRepository = operRepository;
            _operEntRouteRepository = operEntRouteRepository;
            _operService = operService;
            _builtInTypes = options.Value;
        }

        /// <summary>
        /// 新增工艺
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task AddProcessAsync(AddingProcessInput input)
        {
            if (await _processRepository.ExistsAsync(x => x.Process_Id == input.Process_Id || x.Process_Desc == input.Process_Desc))
            {
                throw new CustomException(Messages.ProcessAlreadyExist);
            }
            if (input.MaterialIds.Count <= 0)
            {
                throw new CustomException(Messages.NoBindingMaterial);
            }
            if (input.MaterialIds.Count != input.MaterialIds.Distinct().Count())
            {
                throw new CustomException(Messages.MaterialDuplication);
            }
            var materials = await _materialRepository.Query().Where(x => input.MaterialIds.Contains(x.Id) && x.MaterialClassTypeName == _builtInTypes.BuiltInMaterialClassTypes[0]).ToListAsync();
            if (materials.Count != input.MaterialIds.Count)
            {
                throw new CustomException(Messages.MaterialClassTypeNotProduction);
            }

            var process = _mapper.Map<Process>(input);
            process.Process_Status = 1;//默认启用
            await _processRepository.AddAsync(process);

            var links = new List<ItemProcessLink>();
            foreach (var item in input.MaterialIds)
            {
                var link = new ItemProcessLink()
                {
                    MaterialId = item,
                    Process_Id = input.Process_Id
                };
                links.Add(link);
            }
            await _item_Process_LinkRepository.AddRangeAsync(links);
            int status = await _processRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 编辑工艺
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task UpdateProcessAsync(UpdatingProcessInput input)
        {
            if (input.MaterialIds.Count <= 0)
            {
                throw new CustomException(Messages.NoBindingMaterial);
            }
            if (input.MaterialIds.Count != input.MaterialIds.Distinct().Count())
            {
                throw new CustomException(Messages.MaterialDuplication);
            }
            var materials = await _materialRepository.Query().Where(x => input.MaterialIds.Contains(x.Id) && x.MaterialClassTypeName == _builtInTypes.BuiltInMaterialClassTypes[0]).ToListAsync();
            if (materials.Count != input.MaterialIds.Count)
            {
                throw new CustomException(Messages.MaterialClassTypeNotProduction);
            }

            var process = await _processRepository.FindAsync(x => x.Process_Id == input.Process_Id);
            if (process == null)
            {
                throw new CustomException(Messages.ProcessNotFound);
            }
            process.Spare1 = input.Spare1;
            process.Process_Ver_Id = input.Process_Ver_Id;
            process.Notes = input.Notes;
            process.Last_Edit_At = DateTime.Now;
            await _item_Process_LinkRepository.DeleteAsync(x => x.Process_Id == input.Process_Id);
            if (input.MaterialIds.Count > 0)
            {
                var links = new List<ItemProcessLink>();
                foreach (var item in input.MaterialIds)
                {
                    var link = new ItemProcessLink()
                    {
                        MaterialId = item,
                        Process_Id = input.Process_Id
                    };
                    links.Add(link);
                }
                await _item_Process_LinkRepository.AddRangeAsync(links);
            }
            int status = await _processRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 删除工艺
        /// </summary>
        /// <param name="process_id"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task DeleteProcessAsync(string process_id)
        {
            await _processRepository.DeleteAsync(x => x.Process_Id== process_id);
            await _item_Process_LinkRepository.DeleteAsync(x => x.Process_Id== process_id);
            await _processRepository.SaveAsync();  
        }

        /// <summary>
        /// 查询指定工艺下的工序
        /// </summary>
        /// <param name="process_id"></param>
        /// <returns>List<string></returns>
        public async Task<List<string>> GetProcessOpersAsync(string process_id)
        {
            var opers = await _operRepository.Query().Where(x => x.Process_Id == process_id).ToListAsync();
            var operIds = opers.Select(x => x.Oper_Id).ToList();
            return operIds;
        }

        /// <summary>
        /// 分页查询工艺
        /// </summary>
        /// <param name="input"></param>
        /// <returns>PagedItemsData<ProcessesResult></returns>
        /// <exception cref="CustomException"></exception>
        public async Task<PagedItemsData<ProcessesResult>> GetProcessesAsync(QueryingProcessesInput input)
        {
            Expression<Func<Process, bool>> filter = x => true;
            if (!string.IsNullOrEmpty(input.KeyWord))
            {
                filter = filter.And(x => x.Process_Id.Contains(input.KeyWord) || x.Process_Desc.Contains(input.KeyWord));
            }
            var data = await _processRepository.Query().Where(filter).OrderByDescending(x => x.Created_At).ToPagedAsync(input.PagedInput);
            if (data == null)
            {
                throw new CustomException(Messages.ProcessNotFound);
            }
            var result = _mapper.Map<PagedItemsData<ProcessesResult>>(data);
            var processIds = result.Items.Select(x => x.Process_Id).ToList();
            var opers = await _operRepository.Query().Where(x => processIds.Contains(x.Process_Id)).ToListAsync();
            var operEntRoutes = await _operEntRouteRepository.Query().Where(x => processIds.Contains(x.Process_Id)).ToListAsync();
            foreach (var item in result.Items)
            {
                var currentOpers = opers.Where(x => x.Process_Id == item.Process_Id).ToList();
                var currentOperResults = new List<OpersResult>();
                var currentoperEntRoutes = operEntRoutes.Where(x => x.Process_Id == item.Process_Id).ToList();
                var firstoper = currentOpers.Where(x => x.First_Oper == true).FirstOrDefault();
                var currentOper = firstoper;
                for (int i = 0; i < currentOpers.Count; i++)
                {
                    currentOperResults.Add(_mapper.Map<OpersResult>(currentOper));
                    if (currentOper.Final_Oper == false)
                    {
                        var nextOperId = currentoperEntRoutes.Where(x => x.Input_Oper_Id == currentOper.Oper_Id).FirstOrDefault().Oper_Id;
                        var nextOper = currentOpers.Where(x => x.Oper_Id == nextOperId).FirstOrDefault();
                        currentOper = nextOper;
                    }
                }
                item.OpersResults = currentOperResults;
            }
            return result;
        }

        /// <summary>
        /// 查询指定工艺
        /// </summary>
        /// <param name="process_id"></param>
        /// <returns>CurrentProcessResult</returns>
        /// <exception cref="CustomException"></exception>
        public async Task<CurrentProcessResult> GetProcessAsync(string process_id)
        {
            var process = await _processRepository.FindAsync(x => x.Process_Id == process_id);
            if (process == null)
            {
                throw new CustomException(Messages.ProcessNotFound);
            }
            var data = _mapper.Map<CurrentProcessResult>(process);
            var links = await _item_Process_LinkRepository.Query().Where(x => x.Process_Id == process_id).ToListAsync();
            var materialIds = links.Select(x => x.MaterialId).ToList();
            var materials = await _materialRepository.Query().Where(x => materialIds.Contains(x.Id)).ToListAsync();
            data.Materials = _mapper.Map<List<IdNameResult>>(materials);
            var opers = await _operRepository.Query().Where(x => x.Process_Id == process_id).ToListAsync();
            var originResults = _mapper.Map<List<OpersResult>>(opers);
            var operResults = new List<OpersResult>();
            var operEntRoutes = await _operEntRouteRepository.Query().Where(x => x.Process_Id == process_id).ToListAsync();
            var firstoper = opers.Where(x => x.First_Oper == true).FirstOrDefault();
            var currentOper = firstoper;

            for (int i = 0; i < opers.Count; i++)
            {
                operResults.Add(_mapper.Map<OpersResult>(currentOper));
                if (currentOper.Final_Oper == false)
                {
                    var nextOperId = operEntRoutes.Where(x => x.Input_Oper_Id == currentOper.Oper_Id).FirstOrDefault().Oper_Id;
                    var nextOper = opers.Where(x => x.Oper_Id == nextOperId).FirstOrDefault();
                    currentOper = nextOper;
                }
            }
            data.OpersResults = operResults;
            return  data ;
        }

        /// <summary>
        /// 编辑工艺状态
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task UpdateProcessStatusAsync(UpdatingProcessStatusInput input)
        {
            var process = await _processRepository.FindAsync(x => x.Process_Id == input.Process_Id);
            if (process == null)
            {
                throw new CustomException(Messages.ProcessNotFound);
            }
            if (input.Process_Status != process.Process_Status)
            {
                process.Process_Status = input.Process_Status;
                process.Last_Status_Change_At = DateTime.Now;
                int status = await _processRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
        }

        /// <summary>
        /// 编辑工艺下工序的顺序
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task MovingOperSequenceAsync(MovingOperSequenceInput input)
        {
            if (!await _processRepository.ExistsAsync(x => x.Process_Id == input.Process_Id))
            {
                throw new CustomException(Messages.ProcessNotFound);
            }
            if (input.FromOper_Id == input.ToOper_Id)
            {
                throw new CustomException(Messages.SrcOperMustNotEqualDestOper);
            }
            var allOperIds = new List<string>() { input.FromOper_Id, input.ToOper_Id };
            var opers = await _operRepository.Query().Where(x => allOperIds.Contains(x.Oper_Id) && x.Process_Id == input.Process_Id).ToListAsync();
            if (opers.Count != 2)
            {
                throw new CustomException(Messages.SomeOperNotExist);
            }
            if (!await _operEntRouteRepository.ExistsAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id&&x.Process_Id==input.Process_Id))
            {
                throw new CustomException(Messages.CurrentOpersCantBeMoved);
            }
            var FromOper = opers.FirstOrDefault(x => x.Oper_Id == input.FromOper_Id && x.Process_Id == input.Process_Id);
            var ToOper= opers.FirstOrDefault(x => x.Oper_Id == input.ToOper_Id && x.Process_Id == input.Process_Id);
            if (ToOper.First_Oper == true && FromOper.Final_Oper == false)
            {
                var currentRoute = await _operEntRouteRepository.FindAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Process_Id == input.Process_Id);
                var nextRoute = await _operEntRouteRepository.FindAsync(x => x.Input_Oper_Id == input.FromOper_Id && x.Process_Id == input.Process_Id);
                var newCurrentRoute = new OperEntRoute
                {
                    Ent_Id = currentRoute.Input_Ent_Id,
                    Input_Ent_Id = currentRoute.Ent_Id,
                    Input_Oper_Id = currentRoute.Oper_Id,
                    Input_Percent = 1,
                    Last_Edit_At = DateTime.Now,
                    Last_Edit_By = String.Empty,
                    Last_Edit_Comment = String.Empty,
                    Oper_Id = currentRoute.Input_Oper_Id,
                    Process_Id = input.Process_Id
                };
                var nextCurrentRoute = new OperEntRoute
                {
                    Ent_Id = nextRoute.Ent_Id,
                    Input_Ent_Id = currentRoute.Input_Ent_Id,
                    Input_Oper_Id = currentRoute.Input_Oper_Id,
                    Input_Percent = 1,
                    Last_Edit_At = DateTime.Now,
                    Last_Edit_By = String.Empty,
                    Last_Edit_Comment = String.Empty,
                    Oper_Id = nextRoute.Oper_Id,
                    Process_Id = input.Process_Id
                };

                _operEntRouteRepository.Delete(currentRoute);
                _operEntRouteRepository.Delete(nextRoute);
                int status = await _operEntRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
                await _operEntRouteRepository.AddAsync(newCurrentRoute);
                await _operEntRouteRepository.AddAsync(nextCurrentRoute);
                FromOper.First_Oper = true;
                ToOper.First_Oper = false;
                status = await _operEntRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
            else if (FromOper.Final_Oper == true && ToOper.First_Oper == false)
            {
                var currentRoute = await _operEntRouteRepository.FindAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Process_Id == input.Process_Id);
                var privousRoute = await _operEntRouteRepository.FindAsync(x => x.Oper_Id == input.ToOper_Id && x.Process_Id == input.Process_Id);
                var newCurrentRoute = new OperEntRoute
                {
                    Ent_Id = currentRoute.Input_Ent_Id,
                    Input_Ent_Id = currentRoute.Ent_Id,
                    Input_Oper_Id = currentRoute.Oper_Id,
                    Input_Percent = 1,
                    Last_Edit_At = DateTime.Now,
                    Last_Edit_By = String.Empty,
                    Last_Edit_Comment = String.Empty,
                    Oper_Id = currentRoute.Input_Oper_Id,
                    Process_Id = input.Process_Id
                };
                var privousCurrentRoute = new OperEntRoute
                {
                    Ent_Id = currentRoute.Ent_Id,
                    Input_Ent_Id = privousRoute.Input_Ent_Id,
                    Input_Oper_Id = privousRoute.Input_Oper_Id,
                    Input_Percent = 1,
                    Last_Edit_At = DateTime.Now,
                    Last_Edit_By = String.Empty,
                    Last_Edit_Comment = String.Empty,
                    Oper_Id = currentRoute.Oper_Id,
                    Process_Id = input.Process_Id
                };
                _operEntRouteRepository.Delete(currentRoute);
                _operEntRouteRepository.Delete(privousRoute);
                int status = await _operEntRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
                await _operEntRouteRepository.AddAsync(newCurrentRoute);
                await _operEntRouteRepository.AddAsync(privousCurrentRoute);
                FromOper.Final_Oper = false;
                ToOper.Final_Oper = true;
                status = await _operEntRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
            else if (FromOper.Final_Oper == true && ToOper.First_Oper == true)
            {
                var currentRoute = await _operEntRouteRepository.FindAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Process_Id == input.Process_Id);
                var newCurrentRoute = new OperEntRoute
                {
                    Ent_Id = currentRoute.Input_Ent_Id,
                    Input_Ent_Id = currentRoute.Ent_Id,
                    Input_Oper_Id = currentRoute.Oper_Id,
                    Input_Percent = 1,
                    Last_Edit_At = DateTime.Now,
                    Last_Edit_By = String.Empty,
                    Last_Edit_Comment = String.Empty,
                    Oper_Id = currentRoute.Input_Oper_Id,
                    Process_Id = input.Process_Id
                };
                FromOper.First_Oper = true;
                FromOper.Final_Oper = false;
                ToOper.First_Oper = false;
                ToOper.Final_Oper = true;
                _operEntRouteRepository.Delete(currentRoute);
                await _operEntRouteRepository.AddAsync(newCurrentRoute);
                var status = await _operEntRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
            else
            {
                // 没有firstoper和finaloper
                var currentRoute = await _operEntRouteRepository.FindAsync(x => x.Input_Oper_Id == input.ToOper_Id && x.Oper_Id == input.FromOper_Id && x.Process_Id == input.Process_Id);
                var privousRoute = await _operEntRouteRepository.FindAsync(x => x.Oper_Id == input.ToOper_Id && x.Process_Id == input.Process_Id);
                var nextRoute = await _operEntRouteRepository.FindAsync(x => x.Input_Oper_Id == input.FromOper_Id && x.Process_Id == input.Process_Id);

                var newCurrentRoute = new OperEntRoute
                {
                    Ent_Id = currentRoute.Input_Ent_Id,
                    Input_Ent_Id = currentRoute.Ent_Id,
                    Input_Oper_Id = currentRoute.Oper_Id,
                    Input_Percent = 1,
                    Last_Edit_At = DateTime.Now,
                    Last_Edit_By = String.Empty,
                    Last_Edit_Comment = String.Empty,
                    Oper_Id = currentRoute.Input_Oper_Id,
                    Process_Id = input.Process_Id
                };

                var privousCurrentRoute = new OperEntRoute
                {
                    Ent_Id = currentRoute.Ent_Id,
                    Input_Ent_Id = privousRoute.Input_Ent_Id,
                    Input_Oper_Id = privousRoute.Input_Oper_Id,
                    Input_Percent = 1,
                    Last_Edit_At = DateTime.Now,
                    Last_Edit_By = String.Empty,
                    Last_Edit_Comment = String.Empty,
                    Oper_Id = currentRoute.Oper_Id,
                    Process_Id = input.Process_Id
                };

                var nextCurrentRoute = new OperEntRoute
                {
                    Ent_Id = nextRoute.Ent_Id,
                    Input_Ent_Id = currentRoute.Input_Ent_Id,
                    Input_Oper_Id = currentRoute.Input_Oper_Id,
                    Input_Percent = 1,
                    Last_Edit_At = DateTime.Now,
                    Last_Edit_By = String.Empty,
                    Last_Edit_Comment = String.Empty,
                    Oper_Id = nextRoute.Oper_Id,
                    Process_Id = input.Process_Id
                };

                _operEntRouteRepository.Delete(currentRoute);
                _operEntRouteRepository.Delete(privousRoute);
                _operEntRouteRepository.Delete(nextRoute);
                int status = _operEntRouteRepository.Save();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
                await _operEntRouteRepository.AddAsync(newCurrentRoute);
                await _operEntRouteRepository.AddAsync(privousCurrentRoute);
                await _operEntRouteRepository.AddAsync(nextCurrentRoute);
                status = await _operEntRouteRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
        }

        /// <summary>
        /// 判断工艺是否存在工序
        /// </summary>
        /// <param name="process_id"></param>
        /// <returns>true/false</returns>
        public async Task<bool> ProcessHasOpersAsync(string process_id)
        {
            return await _operRepository.ExistsAsync(x=>x.Process_Id== process_id);
        }

        /// <summary>
        /// 通过工艺路线添加工序
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task AddProcessOpersAsync(AddProcessOpersInput input)
        {
            var response = await ProcessHasOpersAsync(input.Process_Id);
            if (response == true)
            {
                throw new CustomException(Messages.ProcessNotFound);
            }
            await _operService.AddOperationAsync(new OperInput { Process_Id = input.Process_Id, Process_Route_Id = input.Process_Route_Id });
        }
    }
}
